// pageCache 第三层
// page cache的哈希桶映射规则采用的是直接定址法，比如1号桶挂的都是1页的span，2号桶挂的都是2页的span
//  central cache每个桶中的span被切成了一个个对应大小的对象，以供thread cache申请; 
//  而page cache当中的span是没有被进一步切小的，因为page cache服务的是central cache，当central cache没有span时，向page cache申请的是某一固定页数的span
//  而如何切分申请到的这个span就应该由central cache自己来决定。
//  最大挂 128 页：128*4 = 512KB, 【因为 threadCache 线程所申请的单个对象的大小最大也就是 512KB】
// 使用一个大锁：将整个 PageCache 给锁住

#include "common.hpp"
#include "mapTree.hpp"

class PageCache
{
private:
    PageCache(){}
    PageCache(const PageCache&) = delete;
    PageCache& operator=(const PageCache&) = delete;
    ~PageCache(){}

    static PageCache page_inst;
    SpanList page_spanlists_array[PageCache_PageLists];
    std::mutex page_mtx;

    // std::unordered_map<Page_Id, Span*> id_span_map; // 用于快速查找 Span【pageid 到 某个Span的映射】
    TCMalloc_PageMap1<32 - PAGE_SHIFT> id_span_map;

    objectPool<Span> span_pool; // 用于 Span 的内存池
public:
    // 获取 大锁
    std::mutex& getPageMutex()
    {
        return page_mtx;
    }
    // 获取 从对象到 Span 的映射
    Span* objToSpanMap(void* obj)
    {
        // v1ersion 1: 使用 unordered_map
        // Page_Id page_id = (Page_Id)obj >> Page_Shift; // 计算出 page_id
        // std::unique_lock<std::mutex> lock(page_mtx); // 加锁
        // auto it = id_span_map.find(page_id);
        // if(it != id_span_map.end())
        // {
        //     return it->second; // 找到对应的 Span   
        // }
        // else
        // {
        //     assert(false);
        //     return nullptr; // 没有找到对应的 Span
        // }

        // version 2: 使用 TCMalloc_PageMap
        Page_Id page_id = (Page_Id)obj >> Page_Shift; // 计算出 page_id 页号
        Span* ret = (Span*)id_span_map.get(page_id);
        assert(ret != nullptr);
        return ret; // 找到对应的 Span
    }
public:
    // 提供一个全局访问点
    static PageCache* getInstance()
    {
        return &page_inst;
    }
    // CentralCache 从 PageCache 获取 一个K页的Span
    Span* c_getKSpanFromPageCache(size_t kpage)
    {
        // assert(kpage > 0 && kpage < PageCache_PageLists);
        // 大于 256KB 内存申请的检查
        assert(kpage > 0);
        if(kpage >PageCache_PageLists)
        {
            // 大于 256KB 的内存申请，直接找堆
            void* ptr = SystemAlloc(kpage);
            // Span* ret_span = new Span;
            Span* ret_span = span_pool.New(); // 从内存池中申请一个 Span
            ret_span->page_id = (Page_Id)ptr >> Page_Shift;
            ret_span->n = kpage;
            page_spanlists_array[kpage].pushFront(ret_span);
            return ret_span;
        }
        // 接下来就是 < 256KB 的内存申请
        // 1.先检查第 k 个桶内有没有 span
        if(!page_spanlists_array[kpage].isEmpty())
        {
            // return page_spanlists_array[kpage].popFront();

            Span* kSpan = page_spanlists_array[kpage].popFront();
            // 1.1 建立 pageid 到 span 的映射，方便之后 CentralCache 回收小块内存时查找对应的 span
            for(Page_Id i = 0; i < kSpan->n; i++)
            {
                id_span_map[kSpan->page_id + i] = kSpan;
            }
            // 1.2 返回这个 span
            return kSpan;
        }
        // 2.检查一下后面的桶内有没有 span, 如果有可以将其进行切分
        for(size_t i = kpage + 1; i < PageCache_PageLists; i++)
        {
            if(!page_spanlists_array[i].isEmpty())
            {
                Span* nSpan = page_spanlists_array[i].popFront();
                // Span* kSpan = new Span; // 需要填充一些字段，此处的 kSpan 仅仅是一个框架
                Span* kSpan = span_pool.New(); // 从内存池中申请一个 Span
                // 2.1 将nSpan 的头部切 k 页下来
                kSpan->page_id = nSpan->page_id;
                kSpan->n = kpage;
                nSpan->page_id += kpage;
                nSpan->n -= kpage;
                // 2.2 将剩下的span挂到对应 哈希桶内 映射的位置
                page_spanlists_array[nSpan->n].pushFront(nSpan);

                // 星：存储 nSpan 的首尾页号和 nSpan 之间的映射，方便 PageCache 合并 span 时进行前后页的查找
                id_span_map[nSpan->page_id] = nSpan;
                id_span_map[nSpan->page_id + nSpan->n - 1] = nSpan;

                // 2.3 建立 pageid 到 span 的映射，方便之后 CentralCache 回收小块内存时查找对应的 span
                for(Page_Id i = 0; i < kSpan->n; i++)
                {
                    id_span_map[kSpan->page_id + i] = kSpan;
                }
                // 2.4 返回这个 kSpan
                return kSpan;
            }
        }
        // 3. 此时说明后面没有大页的 span 了，这是就向堆申请一个 128 页的 span
        // Span* bigSpan = new Span;
        Span* bigSpan = span_pool.New(); // 从内存池中申请一个 Span
        void* ptr = SystemAlloc(PageCache_PageLists-1);
        bigSpan->page_id = (Page_Id)ptr >> Page_Shift;
        bigSpan->n = Page_Shift - 1;
        page_spanlists_array[bigSpan->n].pushFront(bigSpan);
        // 4.避免重复代码，递归调用自己
        return c_getKSpanFromPageCache(kpage);
    }
    // CentralCache 返回一个 Span 到 PageCache
    void c_returnSpanToPageCache(Span* ret_span)
    {
        // 解决 > 128 页的内存
        if(ret_span->n > PageCache_PageLists - 1)
        {
            // 直接将该 span 交给系统
            void* ptr = (void*)(ret_span->page_id << Page_Shift);
            SystemFree(ptr, ret_span->n << Page_Shift);
            // delete ret_span; // 删除该 span
            span_pool.Delete(ret_span); // 将 ret_span 还给内存池
            return;
        }

        // 1，对于 span 的前后页，尝试进行合并，缓解内存碎片问题
        // 2，向前合并
        while(1)
        {
            Page_Id prev_id = ret_span->page_id - 1;
            auto it = id_span_map.find(prev_id);
            // 2,1 判断前面的页号是否像系统中申请过，若没有则停止向前合并
            if(it == id_span_map.end())
                break;
            // 2.2 判断前面的页号是否对应的 span 被正在使用的，若正在被使用则停止向前合并
            Span* prev_span  = it->second;
            if(prev_span->isUse == true)
                break;
            // 2.3 合并超过 128 页的 span 无法进行管理，停止向前合并
            if(prev_span->n + ret_span->n > PageCache_PageLists - 1)
                break;
            // 2.4 开始向前合并
            ret_span->page_id = prev_span->page_id;
            ret_span->n += prev_span->n;
            // 2.5 将 prevSpan 从对应的双链表中删除
            page_spanlists_array[prev_span->n].eraseSpanInPos(prev_span);
            // 2.6 delete prev_span;
            // delete prev_span;
            span_pool.Delete(prev_span); // 将 prev_span 还给内存池
        }
        // 3. 向后合并q
        while(1)
        {
            Page_Id next_id = ret_span->page_id + ret_span->n;
            auto it = id_span_map.find(next_id);
            // 3.1 判断后面的页号是否像系统中申请过，若没有则停止向后合并
            if(it == id_span_map.end())
                break;
            // 3.2 判断后面的页号是否对应的 span 被正在使用的，若正在被使用则停止向后合并
            Span* next_span = it->second;
            if(next_span->isUse == true)
                break;
            // 3.3 合并超过 128 页的 span 无法进行管理，停止向后合并
            if(ret_span->n + next_span->n > PageCache_PageLists - 1)
                break;
            // 3.4 开始向后合并
            ret_span->n += next_span->n;
            // 3.5 将 nextSpan 从对应的双链表中删除
            page_spanlists_array[next_span->n].eraseSpanInPos(next_span);
            // 3.6 delete next_span;
            // delete next_span;
            span_pool.Delete(next_span); // 将 next_span 还给内存池
        }
        // 4. 将合并后的 ret_span 挂到对应的双链表中
        page_spanlists_array[ret_span->n].pushFront(ret_span);
        // 5. 建立该 span 与其首位页的映射
        id_span_map[ret_span->page_id] = ret_span;
        id_span_map[ret_span->page_id + ret_span->n - 1] = ret_span;
        // 6. 设置该 span 的 isUse 为 false
        ret_span->isUse = false;
    }
};

// 程序一运行我们立马就创建出 PageCache 对象
PageCache PageCache::page_inst;